package com.m4f.city.web.controller.manager;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.cache.CacheException;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.m4f.city.business.domain.Application;
import com.m4f.city.business.domain.InternalUser;
import com.m4f.city.business.domain.Organization;
import com.m4f.city.business.domain.oauth2.OAuth2AccessTokenDS;
import com.m4f.city.business.security.InternalUserDetailService;
import com.m4f.city.business.service.ifc.OrganizationService;
import com.m4f.city.utils.table.DataTable2;
import com.m4f.city.web.form.UserForm;
import com.m4f.gaeweb.web.utils.StackTraceUtil;

@Controller
@Secured(InternalUserDetailService.ROLE_MANAGER)
@RequestMapping("/manager/user")
public class UserManagerController extends ManagerBaseController {
	private static final Logger LOGGER = Logger.getLogger(UserManagerController.class.getName());
	
	public UserManagerController() throws CacheException {
		super();
	}
		
	/*
	 * METHODS FOR ADMIN USERS TO MANAGE THE USERS
	 */
	@RequestMapping(method=RequestMethod.GET)
	public String getForm(Model model, Principal currenUser, Locale locale) {
		try {
			UserForm form = new UserForm(this.serviceLocator.getUserService().createUser());
			form.getUser().setPassword(""); // Clean the password
			
			InternalUser user = getInternalUser(currenUser);
			Organization organization = this.serviceLocator.getOrganizationService().getOrganization(user.getOrganizationId());
			
			model.addAttribute("form", form);
			model.addAttribute("organization", organization);
			
			//model.addAttribute("orgs", getAllOrganization());
		} catch(Exception e) {
			LOGGER.severe(StackTraceUtil.getStackTrace(e));
			return "common.error";
		}
		return "manager.user.form";
	}
	
	@RequestMapping(method=RequestMethod.POST)
	public String save(@ModelAttribute("form") @Valid UserForm form, 
			BindingResult result, Model model, Locale locale) {
		// Check if exist an user with this mail
		boolean updateAction = false;
		try {
			InternalUser oldUser = this.serviceLocator.getUserService().getUser(form.getUser().getEmail());
			if(result.hasErrors() || (oldUser != null && form.getUser().getId() == null)) {
				return "manager.user.form";
			}
			
			// Convert plain password to a md5 password to save it in md5 encoded password.
			if(form.getUser().getPassword() != null && !("").equals(form.getUser().getPassword())) {
				InternalUser newUser = form.getUser();
				Object salt = null;
				Md5PasswordEncoder encoder = new Md5PasswordEncoder();
				newUser.setPassword(encoder.encodePassword(form.getUser().getPassword(), salt));
			}
			
			if(form.getUser().getId() != null) updateAction = true;
			this.serviceLocator.getUserService().save(form.getUser());
			if(updateAction) addSuccessMessage("message.success.admin.user.updated");
			else addSuccessMessage("message.success.admin.user.created");
		} catch(Exception e) {
			if(updateAction) addErrorMessage("message.success.admin.user.updated");
			else addErrorMessage("message.success.admin.user.created");
			LOGGER.severe(StackTraceUtil.getStackTrace(e));
			return "common.error";
		}
		
		return getRedirect("/manager/user/list", locale);
	}
	
	@RequestMapping(value="/edit/{userId}", method=RequestMethod.GET)
	public String edit(@PathVariable Long userId, Model model, Principal currenUser, Locale locale,
			@RequestHeader(required=false, value="referer") String referer, 
			HttpSession session) {
		try {			
			InternalUser user = this.serviceLocator.getUserService().getUser(userId);
			UserForm form = new UserForm(user);
			form.getUser().setPassword(""); // Clean the password
			
			Organization organization = this.serviceLocator.getOrganizationService().getOrganization(user.getOrganizationId());
			
			model.addAttribute("form", form);
			model.addAttribute("organization", organization);
		} catch(Exception e) {
			LOGGER.severe(StackTraceUtil.getStackTrace(e));
			return "common.error";
		}
		return "manager.user.form";
	}
	
	@RequestMapping(value="/list", method=RequestMethod.GET)
	public String getList(Model model, Locale locale) {
		return "manager.user.list";
	}
	
	@RequestMapping(value="/delete/{userId}", method=RequestMethod.GET)
	public String delete(@PathVariable Long userId, Locale locale, 
			@RequestHeader(required=false,value="referer") String referer, 
			@RequestHeader("Host") String host) {
		try {
			InternalUser user = this.serviceLocator.getUserService().getUser(userId);
			this.serviceLocator.getUserService().delete(user);
			addSuccessMessage("message.success.admin.user.deleted");
		} catch(Exception e) {
			addErrorMessage("message.success.admin.user.deleted");
			LOGGER.severe(StackTraceUtil.getStackTrace(e));
			return "common.error";
		}
		
		return getRedirect("/manager/user/list", locale);
	}
	
	@RequestMapping(value="/detail/{userId}", method=RequestMethod.GET)
	public String detail(@PathVariable Long userId, Model model, Locale locale) {
		try {
			InternalUser user = this.serviceLocator.getUserService().getUser(userId);
			user.setPassword(""); // Clean the password
			
			Organization organization = this.serviceLocator.getOrganizationService().getOrganization(user.getOrganizationId());
			ArrayList<Organization> organizations = new ArrayList<Organization>();
			organizations.add(organization);
			
			model.addAttribute("user", user);
			model.addAttribute("organizations", organizations); // Put the organization in a list to reuse the admin root view of user detail.
			
			Collection<OAuth2AccessTokenDS> accessTokens = this.serviceLocator.getOAuth2TokensService().getAllAccessToken(user.getId(), null);
			List<Application> apps = new ArrayList<Application>();
			for(OAuth2AccessTokenDS accessToken : accessTokens) {
				Application app = this.serviceLocator.getApplicationService().getAppliaction(accessToken.getClientId());
				apps.add(app);
			}
			
			model.addAttribute("apps", apps);
		} catch(Exception e) {
			LOGGER.severe(StackTraceUtil.getStackTrace(e));
			return "common.error";
		}
		return "manager.user.detail";
	}
	
	// TESTING
	@RequestMapping(value="/list/table")
	@ResponseBody
	public DataTable2<InternalUser> table3(Principal currentUser, @RequestParam(required=false) Integer iDisplayStart, @RequestParam(required=false) Integer iDisplayLength, /* Pagination */
			@RequestParam(required=false) Integer iSortCol_0, @RequestParam(required=false) String sSortDir_0, /* Sorting */
			@RequestParam(required=false, defaultValue="") String sSearch /* Search */) {
		try {
			InternalUser manager = getInternalUser(currentUser);
			
			// Filter 
			// TODO Now its difficult to filter by columns. So there is a general filter and here we have to set the default column to filter.
			String filterBy = "name";
			String filterValue = sSearch;
			// End filter
			
			String columns[] = new String[]{"id", "name", "email"};
			DataTable2<InternalUser> table = iDisplayLength != null ?
					new DataTable2<InternalUser>(columns, sSortDir_0, iDisplayStart, iDisplayLength) :
					new DataTable2<InternalUser>(columns, sSortDir_0, iDisplayStart);
			table.setRows(this.serviceLocator.getUserService().getUsersByRange(manager.getOrganizationId(), table.getOrdering(iSortCol_0), table.getStart(), table.getEnd())); // TODO add filter params to the service method, like in organizations.
			table.setiTotalDisplayRecords(this.serviceLocator.getUserService().countUsers());
			return table;
		} catch(Exception e) {
			LOGGER.log(Level.SEVERE, StackTraceUtil.getStackTrace(e));
			return new DataTable2<InternalUser>();
		}
	}	
	
	@RequestMapping(value="/organization/description")
	@ResponseBody
	public String getOrganizationDescription(@RequestParam Long organizationId) {
		try {
			Organization organization = this.serviceLocator.getOrganizationService().getOrganization(organizationId);
			return organization != null ? organization.getName() : String.valueOf(organizationId);
		} catch(Exception e) {
			LOGGER.log(Level.SEVERE, StackTraceUtil.getStackTrace(e));
			return String.valueOf(organizationId);
		}
	}	
	
	// Applications management
	@RequestMapping(value="/{userId}/applications", method=RequestMethod.GET)
	public String getApplications(@PathVariable Long userId) {
		return "manager.user.applications";
	}
	
	// This method return the list of applications that are added to the organization, so the user will be able to access them if they are enabled.
	@RequestMapping(value="/{userId}/applications/all")
	@ResponseBody
	public DataTable2<Application> getApplicationsByUser(@PathVariable Long userId, @RequestParam Integer iDisplayStart, @RequestParam Integer iDisplayLength, /* Pagination */
			@RequestParam Integer iSortCol_0, @RequestParam String sSortDir_0, /* Sorting */
			@RequestParam Integer sSearch, @RequestParam String sSearch_0 /* Search */) {
		try {
			// Filter
			InternalUser user = this.serviceLocator.getUserService().getUser(userId);
			Organization organization = this.serviceLocator.getOrganizationService().getOrganization(user.getOrganizationId());
			DataTable2<Application> table = new DataTable2<Application>(new String[]{"id", "name"}, sSortDir_0, iDisplayStart, iDisplayLength);
			table.setRows(this.serviceLocator.getApplicationService().getObjectsByIds(organization.getApplicationIds(), table.getOrdering(iSortCol_0), "", "", table.getStart(), table.getEnd()));
			table.setiTotalDisplayRecords(organization.getApplicationIds() != null ? new Long(organization.getApplicationIds().size()) : 0); // Doing this. We avoid to query to the datastore for obtaining the same expected result.
			return table;
		} catch(Exception e) {
			LOGGER.log(Level.SEVERE, StackTraceUtil.getStackTrace(e));
			return new DataTable2<Application>();
		}
	}
	
	@RequestMapping(value="/{userId}/applications/list")
	@ResponseBody
	public List<Long> getApplicationIds(@PathVariable Long userId) {
		try {
			// Order
			InternalUser user = this.serviceLocator.getUserService().getUser(userId);
			
			return user.getApplicationIds();
		} catch(Exception e) {
			LOGGER.log(Level.SEVERE, StackTraceUtil.getStackTrace(e));
			return new ArrayList<Long>();
		}
	}
	
	@RequestMapping(value="/{userId}/applications/add", method=RequestMethod.POST)
	@ResponseBody
	public boolean addApplicationToOrganization(@PathVariable Long userId, @RequestParam Long appId) {
		try {
			Application app = this.serviceLocator.getApplicationService().getObjectById(appId);
			InternalUser user = this.serviceLocator.getUserService().getUser(userId);
			if(!user.getApplicationIds().contains(app.getId())) {
				user.getApplicationIds().add(app.getId());
				this.serviceLocator.getUserService().save(user);
			}
			return true;
		} catch(Exception e) {
			LOGGER.log(Level.SEVERE, StackTraceUtil.getStackTrace(e));
			return false;
		}
	}
	
	@RequestMapping(value="/{userId}/applications/delete", method=RequestMethod.POST)
	@ResponseBody
	public boolean deleteApplicationFromOrganization(@PathVariable Long userId, @RequestParam Long appId) {
		try {
			Application app = this.serviceLocator.getApplicationService().getObjectById(appId);
			InternalUser user = this.serviceLocator.getUserService().getUser(userId);
			if(user.getApplicationIds().contains(app.getId())) {
				user.getApplicationIds().remove(app.getId());
				this.serviceLocator.getUserService().save(user);
			}
			return true;
		} catch(Exception e) {
			LOGGER.log(Level.SEVERE, StackTraceUtil.getStackTrace(e));
			return false;
		}
	}
}